package com.hup.sokoban.service.common;

import com.hup.sokoban.constant.MapConstant.ElementType;
import com.hup.sokoban.model.common.MapItem;
import com.hup.sokoban.model.common.StepRecord;
import com.hup.sokoban.model.common.StepRecord.CellInfo;
import com.hup.sokoban.view.customView.MapLabel;
import com.hup.sokoban.view.customView.StepRecordPanel;
import com.hup.utils.commons.start.SpringContext;
import lombok.extern.log4j.Log4j2;

import javax.swing.*;
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;

/**
 * 游戏按键功能类
 *
 * @author hugan
 * @date 2020/5/5
 */
@Log4j2
public class PlayerKeyService {

    /**
     * 按下ctrl后,每次移动的步数
     */
    private static final int CTRL_MOVE_STEP = 3;

    public static void bind(PlayerKeyHandler handler) {
        MapLabel mapLabel = handler.getMapLabel();
        StepRecordPanel stepRecordPanel = handler.getStepRecordPanel();
        stepRecordPanel.setMapLabel(mapLabel);
        PlayerKeyService keyFunction = new PlayerKeyService(handler);
        //移动
        mapLabel.registerKeyboardAction(e -> keyFunction.doMove(MOVE_UP, 1),
                KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
        mapLabel.registerKeyboardAction(e -> keyFunction.doMove(MOVE_DOWN, 1),
                KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
        mapLabel.registerKeyboardAction(e -> keyFunction.doMove(MOVE_LEFT, 1),
                KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
        mapLabel.registerKeyboardAction(e -> keyFunction.doMove(MOVE_RIGHT, 1),
                KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
        //一次移动n步
        mapLabel.registerKeyboardAction(e -> keyFunction.doMove(MOVE_UP, CTRL_MOVE_STEP),
                KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
        mapLabel.registerKeyboardAction(e -> keyFunction.doMove(MOVE_DOWN, CTRL_MOVE_STEP),
                KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
        mapLabel.registerKeyboardAction(e -> keyFunction.doMove(MOVE_LEFT, CTRL_MOVE_STEP),
                KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
        mapLabel.registerKeyboardAction(e -> keyFunction.doMove(MOVE_RIGHT, CTRL_MOVE_STEP),
                KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
        //撤销/恢复
        mapLabel.registerKeyboardAction(e -> keyFunction.undoRedo(true),
                KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
        mapLabel.registerKeyboardAction(e -> keyFunction.undoRedo(false),
                KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
    }

    private final PlayerKeyHandler handler;
    private final MapLabel mapLabel;
    private final StepRecordPanel stepRecordPanel;
    private final AudioService audioService;

    private PlayerKeyService(PlayerKeyHandler handler) {
        this.handler = handler;
        this.mapLabel = handler.getMapLabel();
        this.stepRecordPanel = handler.getStepRecordPanel();
        audioService = SpringContext.getBean(AudioService.class);
    }

    /**
     * 实现方向移动功能
     *
     * @param moveCount 要移动的次数
     */
    private void doMove(MoveDirectionHandler handler, int moveCount) {
        if (!this.handler.canMove()) return;

        boolean hadMove = false;
        boolean isWin = false;
        for (int i = 0; i < moveCount; i++) {
            Point ali = findAli();
            if (ali == null) return;

            int[] map = mapLabel.getMapItem().getMap();
            int width = mapLabel.getMapItem().getWidth();
            int height = mapLabel.getMapItem().getHeight();
            int aliIndex = width * ali.y + ali.x;
            ElementType aliDirection = handler.getAliDirection();
            boolean canMove = false;

            if (handler.notEdge(ali, width, height)) {//不是在边缘
                int directionIndex1 = handler.getDirectionIndex1(aliIndex, width);
                ElementType direction1 = ElementType.getLayer1(map[directionIndex1]);
                log.debug("direction1={}", direction1);

                if (direction1 == ElementType.EMPTY) {
                    canMove = true;
                    move(map, aliIndex, aliDirection, directionIndex1);
                } else if (direction1 == ElementType.BIRD) {
                    if (handler.notEdge1(ali, width, height)) {//不是在边缘
                        int directionIndex2 = handler.getDirectionIndex2(aliIndex, width);
                        ElementType direction2 = ElementType.getLayer1(map[directionIndex2]);
                        canMove = direction2 == ElementType.EMPTY;
                        if (canMove) {
                            move(map, aliIndex, aliDirection, directionIndex1, directionIndex2);
                        }
                    }
                }
            }
            log.debug("canMove={}", canMove);
            if (!canMove) {
                //不能走,但要转向
                if (ElementType.hasCage(map[aliIndex])) {
                    map[aliIndex] = aliDirection.intValueWithCage();
                } else {
                    map[aliIndex] = aliDirection.intValue;
                }
                log.debug("已经不能移动,跳出for");//主要处理,移动多步时的break
                //当前不能移动,上一步又没胜出:本次操作肯定不会胜出
                break;
            } else {
                hadMove = true;
                isWin = checkIsWin();
                if (isWin) {
                    log.debug("移动后胜出了");
                    break;
                }
            }
        }

        mapLabel.notifyDataUpdated();
        this.handler.afterMoved();
        if (hadMove) audioService.move();
        if (isWin) this.handler.onWinning();
    }

    private void undoRedo(boolean isUndo) {
        handler.beforeUndoRedo(isUndo);
        boolean changed;
        if (isUndo) {
            changed = stepRecordPanel.undo();
        } else {
            changed = stepRecordPanel.redo();
        }
        if (changed) {
            audioService.move();
        }
    }

    /**
     * @return 阿狸的位置, 如果存在多个, 则返回第一个
     */
    private Point findAli() {
        int width = mapLabel.getMapItem().getWidth();
        int[] map = mapLabel.getMapItem().getMap();
        for (int i = 0; i < map.length; i++) {
            int val = map[i];
            if (ElementType.isAli(val)) {
                int col = i % width;
                int row = i / width;
                log.debug("res=[{},{}], i={}, width={}", col, row, i, width);
                return new Point(col, row);
            }
        }
        return null;
    }

    /**
     * 从 aliIndex 移动到 directionIndex1
     *
     * @param aliIndex 开始移动的index
     * @param aliDirection 移动后阿狸的方向
     * @param directionIndex1 移动的后的index,原来是空或笼子
     */
    private void move(int[] map, int aliIndex, ElementType aliDirection, int directionIndex1) {
        StepRecord stepRecord = new StepRecord();
        CellInfo cell1 = new CellInfo(aliIndex, map[aliIndex]);
        CellInfo cell2 = new CellInfo(directionIndex1, map[directionIndex1]);

        if (ElementType.hasCage(map[aliIndex])) {
            map[aliIndex] = ElementType.CAGE.intValue;
        } else {
            map[aliIndex] = ElementType.EMPTY.intValue;
        }
        if (ElementType.hasCage(map[directionIndex1])) {
            map[directionIndex1] = aliDirection.intValueWithCage();
        } else {
            map[directionIndex1] = aliDirection.intValue;
        }

        cell1.valueAfter = map[aliIndex];
        cell2.valueAfter = map[directionIndex1];
        stepRecord.add(cell1);
        stepRecord.add(cell2);
        stepRecordPanel.addRecord(stepRecord);
    }

    /**
     * @param directionIndex1 移动的后的index,原来是小鸟(可能有笼子)
     * @param directionIndex2 小鸟移动后的index
     */
    private void move(int[] map, int aliIndex, ElementType aliDirection, int directionIndex1, int directionIndex2) {
        StepRecord stepRecord = new StepRecord();
        CellInfo cell1 = new CellInfo(aliIndex, map[aliIndex]);
        CellInfo cell2 = new CellInfo(directionIndex1, map[directionIndex1]);
        CellInfo cell3 = new CellInfo(directionIndex2, map[directionIndex2]);

        if (ElementType.hasCage(map[aliIndex])) {
            map[aliIndex] = ElementType.CAGE.intValue;
        } else {
            map[aliIndex] = ElementType.EMPTY.intValue;
        }
        if (ElementType.hasCage(map[directionIndex1])) {
            map[directionIndex1] = aliDirection.intValueWithCage();
        } else {
            map[directionIndex1] = aliDirection.intValue;
        }
        if (ElementType.hasCage(map[directionIndex2])) {
            map[directionIndex2] = ElementType.CAGE_BIRD.intValue;
        } else {
            map[directionIndex2] = ElementType.BIRD.intValue;
        }

        cell1.valueAfter = map[aliIndex];
        cell2.valueAfter = map[directionIndex1];
        cell3.valueAfter = map[directionIndex2];
        stepRecord.add(cell1);
        stepRecord.add(cell2);
        stepRecord.add(cell3);
        stepRecordPanel.addRecord(stepRecord);
    }

    /**
     * @return 是否通关
     */
    private boolean checkIsWin() {
        MapItem mapItem = mapLabel.getMapItem();
        int[] map = mapItem.getMap();

        //阿狸不在笼子中
        Point ali = findAli();
        if (ali == null) return false;
        int aliIndex = mapItem.getWidth() * ali.y + ali.x;
        if (ElementType.hasCage(map[aliIndex])) return false;

        //没有空笼子
        if (mapItem.getAliArea().isEmpty()) return false;
        for (Integer i : mapItem.getAliArea()) {
            if (map[i] == ElementType.CAGE.intValue) return false;
        }

        //符合上面条件:胜出
        return true;
    }

    private static final MoveDirectionHandler MOVE_UP = new MoveDirectionHandler() {
        @Override
        public ElementType getAliDirection() {
            return ElementType.ALI_BACK;
        }

        @Override
        public boolean notEdge(Point ali, int width, int height) {
            return ali.y != 0;
        }

        @Override
        public int getDirectionIndex1(int aliIndex, int width) {
            return aliIndex - width;
        }

        @Override
        public boolean notEdge1(Point ali, int width, int height) {
            return ali.y != 1;
        }

        @Override
        public int getDirectionIndex2(int aliIndex, int width) {
            return aliIndex - width * 2;
        }
    };
    private static final MoveDirectionHandler MOVE_DOWN = new MoveDirectionHandler() {
        @Override
        public ElementType getAliDirection() {
            return ElementType.ALI_FRONT;
        }

        @Override
        public boolean notEdge(Point ali, int width, int height) {
            return ali.y != height - 1;
        }

        @Override
        public int getDirectionIndex1(int aliIndex, int width) {
            return aliIndex + width;
        }

        @Override
        public boolean notEdge1(Point ali, int width, int height) {
            return ali.y != height - 2;
        }

        @Override
        public int getDirectionIndex2(int aliIndex, int width) {
            return aliIndex + width * 2;
        }
    };
    private static final MoveDirectionHandler MOVE_LEFT = new MoveDirectionHandler() {
        @Override
        public ElementType getAliDirection() {
            return ElementType.ALI_LEFT;
        }

        @Override
        public boolean notEdge(Point ali, int width, int height) {
            return ali.x != 0;
        }

        @Override
        public int getDirectionIndex1(int aliIndex, int width) {
            return aliIndex - 1;
        }

        @Override
        public boolean notEdge1(Point ali, int width, int height) {
            return ali.x != 1;
        }

        @Override
        public int getDirectionIndex2(int aliIndex, int width) {
            return aliIndex - 2;
        }
    };
    private static final MoveDirectionHandler MOVE_RIGHT = new MoveDirectionHandler() {
        @Override
        public ElementType getAliDirection() {
            return ElementType.ALI_RIGHT;
        }

        @Override
        public boolean notEdge(Point ali, int width, int height) {
            return ali.x != width - 1;
        }

        @Override
        public int getDirectionIndex1(int aliIndex, int width) {
            return aliIndex + 1;
        }

        @Override
        public boolean notEdge1(Point ali, int width, int height) {
            return ali.x != width - 2;
        }

        @Override
        public int getDirectionIndex2(int aliIndex, int width) {
            return aliIndex + 2;
        }
    };

    /**
     * 不同移动方向的处理器
     */
    private interface MoveDirectionHandler {

        /**
         * @return 要移动的方向
         */
        ElementType getAliDirection();

        /**
         * @param ali 阿狸的位置
         * @param width 地图大小
         * @param height 地图大小
         * @return [阿狸的位置]是不是在移动方向的边界;false-是,不能移动
         */
        boolean notEdge(Point ali, int width, int height);

        /**
         * @param aliIndex 阿狸的位置
         * @param width 地图宽度
         * @return 阿狸[要移动到的下一格]的位置
         */
        int getDirectionIndex1(int aliIndex, int width);

        /**
         * @return 阿狸[要移动到的下一格]是不是在移动方向的边界;false-是,不能移动
         */
        boolean notEdge1(Point ali, int width, int height);

        int getDirectionIndex2(int aliIndex, int width);
    }

    public interface PlayerKeyHandler {
        MapLabel getMapLabel();

        StepRecordPanel getStepRecordPanel();

        default boolean canMove() {
            return true;
        }

        default void afterMoved() {}

        /**
         * 按下[撤销/恢复]按键前
         */
        default void beforeUndoRedo(boolean isUndo) {}

        /**
         * 胜出
         */
        void onWinning();
    }

}
